/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: json.h
 *
 * Purpose: json reader and writer implementation
 *
 * Developer:
 *   wen.gu , 2019-07-04
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#ifndef __COLLIE_CORE_JSON_H__
#define __COLLIE_CORE_JSON_H__

#include <vector>
#include <memory>
#include <map>
#include <assert.h>

#include "collie/core/types.h"


/******************************************************************************
 **    MACROS
 ******************************************************************************/

#define JSON_ASSERT(condition)                                                 \
  {                                                                            \
    if (!(condition)) {                                                        \
      collie::core::ThrowLogicError("assert json failed");                      \
    }                                                                          \
  }

#define JSON_FAIL_MESSAGE(message)                                             \
  {                                                                            \
    collie::core::ThrowLogicError(message);                                     \
    abort();                                                                   \
  }


#define JSON_ASSERT_MESSAGE(condition, message)                                \
  if (!(condition)) {                                                          \
    JSON_FAIL_MESSAGE(message);                                                \
  }

 /******************************************************************************
 **    TYPE DEFINITIONS
 ******************************************************************************/
namespace collie
{
namespace core
{

using ArrayIndex = uint32_t;

/** \brief Type of precision for formatting of real values.
 */
enum class PrecisionType: uint8_t
{
    significantDigits = 0, ///< we set max number of significant digits in string
    decimalPlaces          ///< we set max number of digits after "." in string
};

enum class ValueType: uint8_t
{
    Null = 0, /*< 'null' value  */ 
    Int,      /*< signed integer value */ 
    Uint,     /*< unsigned integer value */ 
    Real,     /*< double value */ 
    String,   /*< UTF-8 string value */ 
    Boolean,  /*< bool value */ 
    Array,    /*< array value (ordered list) */ 
    Object,    /*< object value (collection of name/value pairs). */ 
    Error     /**< invalid value type */
};

enum class CommentPlacement: uint8_t
{
    Before = 0,      /*< a comment placed on the line before a value */
    AfterOnSameLine, /*< a comment just after a value on the same line */

    NumberOfComment  /**< max number of comment placement type */
};

 /******************************************************************************
 **    CLASSES/FUNCTIONS DEFINITIONS
 ******************************************************************************/



class CORE_CLASS Value
{
public:
    using LargestUInt = uint64_t;
    using LargestInt = int64_t;
    using Int = int32_t;
    using UInt = uint32_t;
    using Int64 = int64_t;
    using UInt64 = uint64_t;
    using ObjectMembers = std::map<std::string, Value>;
    using ArrayElements = std::vector<Value>;
public:
    /// Minimum signed integer value that can be stored in a Json::Value.
    static const LargestInt minLargestInt;
    /// Maximum signed integer value that can be stored in a Json::Value.
    static const LargestInt maxLargestInt;
    /// Maximum unsigned integer value that can be stored in a Json::Value.
    static const LargestUInt maxLargestUInt;

    /// Minimum signed int value that can be stored in a Json::Value.
    static const int32_t minInt;
    /// Maximum signed int value that can be stored in a Json::Value.
    static const int32_t maxInt;
    /// Maximum unsigned int value that can be stored in a Json::Value.
    static const uint32_t maxUInt;

    /// Minimum signed 64 bits int value that can be stored in a Json::Value.
    static const int64_t minInt64;
    /// Maximum signed 64 bits int value that can be stored in a Json::Value.
    static const int64_t maxInt64;
    /// Maximum unsigned 64 bits int value that can be stored in a Json::Value.
    static const uint64_t maxUInt64;
public:
    /// Default precision for real value for string representation.
    static const uint32_t defaultRealPrecision;

    static const Value& null; ///< We regret this reference to a global instance;
                          ///< prefer the simpler Value().
    static const Value& nullRef; ///< just a kludge for binary-compatibility; same
                                 ///< as null
    static Value const& nullSingleton(); ///< Prefer this to null or nullRef.

public: /** constructors and destructor */    
    Value(ValueType type = ValueType::Null); /** construct with value type */
    Value(bool val); 
    Value(int32_t val); 
    Value(uint32_t val); 
    Value(int64_t val);
    Value(uint64_t val);
    Value(double val);
    Value(const std::string& val);

    Value(const Value& other);
    Value(Value&& other) noexcept;

    ~Value();

public: /** override assign value operator*/
    Value& operator=(const Value& other);
    Value& operator=(Value&& other) noexcept;

    Value& operator=(bool val);
    Value& operator=(int32_t val);
    Value& operator=(uint32_t val);
    Value& operator=(int64_t val);
    Value& operator=(uint64_t val);
    Value& operator=(double val);
    Value& operator=(const std::string& val);

public: /** for compare */
    /* Compare payload only, not comments etc. */
    bool operator==(const Value& other) const;
    bool operator!=(const Value& other) const;

public: /** value type check */
    bool isNull() const;
    bool isBool() const;
    bool isInt() const;    
    bool isUInt() const;
    bool isInt64() const;
    bool isUInt64() const;
    bool isIntegral() const;
    bool isDouble() const;
    bool isNumeric() const;
    bool isString() const;
    bool isArray() const;
    bool isObject() const;
    ValueType type() const;

public: /** get value(payload) */
    int32_t asInt() const;
    uint32_t asUInt() const;
    int64_t asInt64() const;
    uint64_t asUInt64() const;
    bool asBool() const;
    float asFloat() const;
    double asDouble() const;
    std::string asString() const;
    const char* asCString() const;

public: /** array or obejct operation */
     /** Number of values in array or object */
    ArrayIndex size() const;

    /** \brief Return true if empty array, empty object, or null;
     *         otherwise, false. 
     */
    bool empty() const;

    /** Return !isNull() */
    explicit operator bool() const;


    /** Remove all object members and array elements.
     * \pre type() is array, object, or null
     * \post type() is unchanged
     */
    void clear();

    /** Access an array element (zero based index).
     * If the array contains less than index element, then throw exception
     * in the array so other its size is index+1.
     * \pre isArray() == true
     * \param index Zero-based index of element.
     */
    Value& operator[](ArrayIndex index);

    /** Access an array element (zero based index).
     * If the array contains less than index element, then throw exception
     * in the array so other its size is index+1.
     * \pre isArray() == true
     * \param index Zero-based index of element.
     */
    const Value& operator[](ArrayIndex index) const;


    /* If the array contains at least index+1 elements,
     *  returns the element value, otherwise returns defaultValue.
     *
     */
    Value get(ArrayIndex index, const Value& defaultValue) const;
    
    /* Return true if index < size(). */
    bool isValidIndex(ArrayIndex index) const;

    /* \brief Append a Value at the end of the array. */
    Value& append(const Value& value);
    Value& append(Value&& value);

    /* Access an object value by name, create a null member if it does not exist.
     * \param key may contain embedded nulls.
     */
    Value& operator[](const std::string& key);
    
    /* Access an object value by name, returns null if there is no member with
     * that name.
     * \param key may contain embedded nulls.
     */
    const Value& operator[](const std::string& key) const;

    /* Return the member named key if it exist, defaultValue otherwise.
     * \note deep copy
     * \param key may contain embedded nulls.
     */
    Value get(const std::string& key, const Value& defaultValue) const;

public: /** object member or array element operation */
    /* \pre type() is object or null
     * \post type() is unchanged
     */
    void removeMember(const std::string& key);

    /** \brief Remove the indexed array element.
     *  O(n) expensive operations.
     * \return true if removed (no exceptions)
     */
    bool removeIndex(ArrayIndex index);

    /* Return true if the object has a member named key.
     * \param key may contain embedded nulls.
     */
    bool isMember(const std::string& key) const;

    /* \brief Return a list of the member names.
     *
     * If null, return an empty list.
     * \pre type() is object or null
     * \post if type() was null, it remains null
     */
    std::vector<std::string> getMembers();

 public:
     /* \brief Return all members of an object .
     *
     * \pre type() is object
     * If type() not object, then thow exception
     */
     const ObjectMembers& getObjectMembers() const;

     /* \brief Return all element of an array .
      *
      * \pre type() is array
      * If type() not array, then thow exception
      */
     const ArrayElements& getArrayElements() const;
private:
    class impl;
    std::unique_ptr<impl> mImpl;
};

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

/// used internally
CORE_NO_RETURN void ThrowRuntimeError(const std::string& msg);
/// used internally
CORE_NO_RETURN void ThrowLogicError(const std::string& msg);

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
std::string CORE_API ValueToString(int64_t val);
std::string CORE_API ValueToString(uint64_t val);

std::string CORE_API ValueToString(double val,
    unsigned int precision = Value::defaultRealPrecision,
    PrecisionType precisionType = PrecisionType::significantDigits);

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

/**\brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document.
 * \param document UTF-8 encoded string containing the document to read.
 * \param root [out] Contains the root value of the document if it was
 *                   successfully parsed.
 *
 * \return true: if the document was successfully parsed,
           false: if an error occurred.
 */
CORE_API bool JsonParse(const std::string& document,
                         Value& root);


/** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document.
 * \param beginDoc [in] Pointer on the beginning of the UTF-8 encoded string
                        of the document to read.
 * \param endDoc   [in] Pointer on the end of the UTF-8 encoded string of the
                        document to read.
 * \param root [out] Contains the root value of the document if it was
 *                   successfully parsed.
 *
 * \return true: if the document was successfully parsed,
           false: if an error occurred.
 */
CORE_API bool JsonParse(const char* beginDoc,
                         const char* endDoc,
                         Value& root);

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


/** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format.
 * \param root Value to serialize.
 * \return true: success, false: failed.
 */
CORE_API bool JsonWriteRaw(const Value& root, std::string& outStr);


/* 
 * The rules for line breakand indent are as follow :
 *  -Object value :
 *      -if empty then print{} without indentand line break
 *      -if not empty the print '{', line break& indent, print one value per
 *       line and then unindentand line breakand print '}'.

 *  -Array value :
 *      -if empty then print[] without indentand line break
 *      -if the array contains no object value, empty array or some other
 *       value types, and all the values fit on one lines, then print the 
 *       array on a single line.

 *  -otherwise, 
        it the values do not fit on one line, or the array contains
 *      object or non empty array, then print one value per line.
 //////////////////////////////////////////////////////////////////////////////
 * \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format
          with Styled way.
 * \param root Value to serialize.
 * \param outStr  String containing the JSON document that 
 *                represents the root value
 * \return true: success, false: failed.

 */
CORE_API bool JsonWriteStyled(const Value& root, std::string& outStr);


} /** namespace core */
} /** namespace collie */

#endif /** !__COLLIE_CORE_JSON_H__ */
